//
//  rebuild.cpp
//  
//
//  Created by Nikita Luzyanin on 30.11.13.
//
//

#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdlib.h>
#include <stdio.h>
#include "functions.cpp"
using namespace std;

int rebuildRaidType1(int numberOfDisks, int numberOfBlocks, int sizeOfBlock, int lengthOfMetaBlock, char missingDisksNames[5][100], int numberOfDisksMissing)
{
    fstream HF;
    char diskName[10], diskNumber[10], ch;
    bool firstRun = true;
    char *rebuildedDiskData;
    int lengthOfDisk = lengthOfMetaBlock + numberOfBlocks * sizeOfBlock + 1;
    bool diskIsMissing;
    rebuildedDiskData = new char [lengthOfDisk];
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        
        diskIsMissing = false;
        for (int i = 0; i < numberOfDisksMissing; i ++) {
            if (strcmp(diskName, missingDisksNames[i]) == 0) {
                diskIsMissing = true;
            }
        }
        
        if (diskIsMissing == false) {
            HF.open(diskName);
            for (int i = 0; i < lengthOfDisk; i ++) {
                ch = HF.get();
                if (firstRun == true) {
                    rebuildedDiskData[i] = ch;
                } else {
                    if (rebuildedDiskData[i] != ch) {
                        cout << "Disks are not equal\n";
                        return 1;
                    }
                }
            }
            firstRun = false;
            HF.close();
        }
    }
    
    ofstream RF;
    for (int j = 0; j < numberOfDisksMissing; j ++) {
        RF.open(missingDisksNames[j]);
        for (int i = 0; i < lengthOfDisk; i++) {
            RF << rebuildedDiskData[i];
        }
        RF.close();
    }
    
    return 0;
}

int rebuildRaidType345(int numberOfDisks, int numberOfBlocks, int sizeOfBlock, int lengthOfMetaBlock, char missingDiskName[100])
{
    char diskName[10];
    char diskNumber[10];
    int disksForData = numberOfDisks - 1;
    char *rebuildedDiskData;
    int lengthOfDisk = lengthOfMetaBlock + numberOfBlocks * sizeOfBlock + 1;
    rebuildedDiskData = new char [lengthOfDisk];
    
    fstream HF;
    bool firstRun = true;
    char ch;
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        if (strcmp(diskName, missingDiskName) != 0) {
            HF.open(diskName);
            for (int i = 0; i < lengthOfDisk; i ++) {
                ch = HF.get();
                if (firstRun == true) {
                    rebuildedDiskData[i] = ch;
                } else {
                    rebuildedDiskData[i] = rebuildedDiskData[i] xor ch;
                }
            }
        }
        firstRun = false;
        HF.close();
    }
    
    ofstream RF;
    RF.open(missingDiskName);
    for (int i = 0; i < lengthOfDisk; i++) {
        RF << rebuildedDiskData[i];
    }
    RF.close();
    
    return 0;
}

int rebuildRaidType01(int numberOfDisks, int numberOfBlocks, int sizeOfBlock, int lengthOfMetaBlock, char missingDisksNames[5][100], int numberOfDisksMissing)
{
    ofstream RF;
    fstream HF;
    int numberOfDisksFor0Type = numberOfDisks / 2;
    int numberOfDisksFor1Type = numberOfDisks / 2;
    int lengthOfDisk = lengthOfMetaBlock + numberOfBlocks * sizeOfBlock + 1;
    char diskName[10], diskNumber[10];
    char rebuildDiskName[10];
    char ch;
    
    if (numberOfDisksMissing > numberOfDisks / 2) {
        cout << "Too many disks are missing\n";
        return 1;
    }
    
    for (int numberOfMissingDisk = 0; numberOfMissingDisk < numberOfDisksMissing; numberOfMissingDisk ++) {
        char helperC = missingDisksNames[numberOfMissingDisk][strlen(missingDisksNames[numberOfMissingDisk]) - 1];
        int helper = helperC - '0';
        
        sprintf(diskNumber, "%i", helper);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        
        if (helper < numberOfDisksFor0Type) {
            sprintf(diskNumber, "%i", helper + numberOfDisksFor0Type);
            strcpy(rebuildDiskName, "disk");
            strcat(rebuildDiskName, diskNumber);
            if (checkFile(rebuildDiskName) != 0) {
                cout << "Can not rebuild. Not enough information\n";
                return 1;
            }
        } else if (helper >= numberOfDisksFor0Type) {
            sprintf(diskNumber, "%i", helper - numberOfDisksFor0Type);
            strcpy(rebuildDiskName, "disk");
            strcat(rebuildDiskName, diskNumber);
            if (checkFile(rebuildDiskName) != 0) {
                cout << "Can not rebuild. Not enough information\n";
                return 1;
            }
        }
        
        HF.open(rebuildDiskName);
        RF.open(diskName);
        ch = HF.get();
        while (!HF.eof()) {
            RF << ch;
            ch = HF.get();
        }
        HF.close();
        RF.close();
    }
    
    return 0;
}

int rebuildRaidType10(int numberOfDisks, int numberOfBlocks, int sizeOfBlock, int lengthOfMetaBlock, char missingDisksNames[5][100], int numberOfDisksMissing)
{
    ofstream RF;
    fstream HF;
    int numberOfDisksFor0Type = numberOfDisks / 2;
    int numberOfDisksFor1Type = numberOfDisks / 2;
    int lengthOfDisk = lengthOfMetaBlock + numberOfBlocks * sizeOfBlock + 1;
    char diskName[10], diskNumber[10];
    char rebuildDiskName[10];
    char ch;
    
    if (numberOfDisksMissing > numberOfDisks / 2) {
        cout << "Too many disks are missing\n";
        return 1;
    }
    
    for (int numberOfMissingDisk = 0; numberOfMissingDisk < numberOfDisksMissing; numberOfMissingDisk ++) {
        char helperC = missingDisksNames[numberOfMissingDisk][strlen(missingDisksNames[numberOfMissingDisk]) - 1];
        int helper = helperC - '0';
        
        sprintf(diskNumber, "%i", helper);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        
        if ((helper == numberOfDisks / 2 - 1)||(helper == numberOfDisks - 1)) {
            sprintf(diskNumber, "%i", helper - 1);
            strcpy(rebuildDiskName, "disk");
            strcat(rebuildDiskName, diskNumber);
            if (checkFile(rebuildDiskName) != 0) {
                cout << "Can not rebuild. Not enough information\n";
                return 1;
            }
        }  else {
            sprintf(diskNumber, "%i", helper + 1);
            strcpy(rebuildDiskName, "disk");
            strcat(rebuildDiskName, diskNumber);
            if (checkFile(rebuildDiskName) != 0) {
                cout << "Can not rebuild. Not enough information\n";
                return 1;
            }
        }
        
        HF.open(rebuildDiskName);
        RF.open(diskName);
        ch = HF.get();
        while (!HF.eof()) {
            RF << ch;
            ch = HF.get();
        }
        HF.close();
        RF.close();
    }
    
    
    return 0;
}

int main(int argc, char* argv[])
{
    int numberOfDisks, numberOfBlocks, sizeOfBlock, lengthOfMetaBlock;
    char raidType[2];
    char missingDisksNames[5][100];
    int numberOfDisksMissing;
    if (checkDisks(numberOfDisks, numberOfBlocks, sizeOfBlock, raidType, lengthOfMetaBlock, missingDisksNames, numberOfDisksMissing) != 0) {
        return 1;
    } else if (numberOfDisksMissing == 0) {
        cout << "No disk missing\n";
        return 1;
    }
    
    bool completed = true;
    if (strcmp(raidType, "0") == 0) {
        cout << "This type is unrebealdable\n";
    } else if ((strcmp(raidType, "1")  == 0)&&(numberOfDisksMissing < numberOfDisks)) {
        if (rebuildRaidType1(numberOfDisks, numberOfBlocks, sizeOfBlock, lengthOfMetaBlock, missingDisksNames, numberOfDisksMissing) != 0) {
            cout << "Read files are different\n";
            completed = false;
        }
    } else if (((strcmp(raidType, "3") == 0)||(strcmp(raidType, "4") == 0)||(strcmp(raidType, "5") == 0))&&(numberOfDisksMissing == 1)) {
        if (rebuildRaidType345(numberOfDisks, numberOfBlocks, sizeOfBlock, lengthOfMetaBlock, missingDisksNames[numberOfDisksMissing - 1]) != 0) {
            completed = false;
        }
    } else if ((raidType[0] == '0')&&(raidType[1] == '1')) {
        if (rebuildRaidType01(numberOfDisks, numberOfBlocks, sizeOfBlock, lengthOfMetaBlock, missingDisksNames, numberOfDisksMissing) != 0) {
            completed = false;
        }
    } else if ((raidType[0] == '1')&&(raidType[1] == '0')) {
        if (rebuildRaidType10(numberOfDisks, numberOfBlocks, sizeOfBlock, lengthOfMetaBlock, missingDisksNames, numberOfDisksMissing) != 0) {
            completed = false;
        }
    }
        
    if (completed == false) {
        cout << "Mistake while rebuilding\n";
        return 1;
    } else {
        for (int i = 0; i < numberOfDisksMissing ; i ++ ) {
            cout << missingDisksNames[i] <<" rebuilded\n";
        }
    }
    
    return 0;
}